#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "mem_structures.h"
#include "mem_macros.h"
#include "mem_functions.h"
#include "syntax.h"


/*
 * Private Functions Declarations
 */

/** Create a new stand-alone chunk **/
chunk_t* priv_new_chunk();

/** Shift a chunks contents right by one **/
void priv_shift_chunk_right(chunk_t* chunk);

/** Shift part of a chunk right (create a space) **/
void priv_shift_chunk_right_partial(chunk_t* chunk, int point);

/** Free a chunk. Do NOT do any other list management **/
void priv_free_chunk(chunk_t* chunk);

/** Remove and free a chunk. Stitching the list back together **/
void priv_chop_chunk(chunk_t* chunk, buffer_t* buffer);

/** Insert a chunk, after the supplied argument. **/
void priv_insert_chunk_after(chunk_t* chunk, buffer_t* buffer);

/** Get the chunk that contains the position. This is "fold aware"
 * Folded lines are NEVER counted in the position location */
chunk_t* priv_get_chunk_by_position(buffer_t* buffer, POSITION p);


/*---------------------------------------------------------------------------*/


/*
 * Public Functions
 */

/*
 * Create a new buffer
 */
buffer_t* mem_newBuffer()
{
    DEBUG_LOC();
    buffer_t* localBuffer = (buffer_t*)calloc(1,sizeof(buffer_t));

    localBuffer->head = priv_new_chunk();
    localBuffer->tail=localBuffer->head;
    localBuffer->num_chunks = 1;

    localBuffer->context = localBuffer->head;

    return localBuffer;
}

/*
 * Delete a buffer and all of its contents
 */
void mem_delBuffer(buffer_t* buffer)
{
    DEBUG_LOC();
    /* Assertions */
    assert(buffer != NULL);

    /* Variables */
    int i;
    chunk_t* chunk_ptr;
    chunk_t* chunk_death;

    /* Empty buffers are strange
     * lets check the pointers.
     * lets print a warning.
     */
    if(buffer->num_chunks==0)
    {
        assert(buffer->head==NULL);
        assert(buffer->tail==NULL);
        DEBUG_WARN("Deleting empty buffer.");
        free(buffer);
        return;
    }

    /* Iterate all chunks */
    chunk_ptr = buffer->head;
    for(i=0;chunk_ptr!=NULL;i++)
    {
        chunk_death = chunk_ptr;
        chunk_ptr = chunk_ptr->next;
        /* The list ends before it should */
        if(i > buffer->num_chunks)
            DEBUG_WARN("List claims to be shorter than it is");

        priv_free_chunk(chunk_death);
    }

    free(buffer);
}

/*
 * Add a character to a buffer, NOT A NEWLINE!!
 */
void mem_addChar(buffer_t* buffer, CHARACTER c, POSITION p)
{
    DEBUG_LOC();
    /* Assertions */
    assert(buffer!=NULL);
    assert(p>=0);

    /* Variables */
    chunk_t* chunk_pos;
    chunk_t* chunk_tmp;
    char message[MSG_LEN];

    /* chunk_pos will always return us a valid chunk
     * even if it has to create one.
     */
    chunk_pos = priv_get_chunk_by_position(buffer, p);
    
    if(chunk_pos->local_pos < chunk_pos->length_sans_newline)
    {
        priv_shift_chunk_right_partial(chunk_pos,chunk_pos->local_pos);  
    }
    else
    {
        INCREMENT_LENGTH(chunk_pos->length,chunk_pos->length_sans_newline,c);
    }
        
    chunk_pos->mem[chunk_pos->local_pos] = c;

#if 0
    /*Check to see our char will fit in this chunk*/
    if(chunk_pos->length<CHUNK_SIZE-1)
    {
        /* See if our char is at the end of the chunk
         * if it isn't, local_pos will point to somewhere 
         * in the middle of the buffer so we have to make
         * some space*/
        if(chunk_pos->local_pos < chunk_pos->length)
        {
            priv_shift_chunk_right_partial(chunk_pos,chunk_pos->local_pos);
        }
        else
        {
            INCREMENT_LENGTH(chunk_pos->length,
                             chunk_pos->length_sans_newline,c);
           chunk_pos->local_pos = p;
        }

        snprintf(message,MSG_LEN,
                 "Writing [%c] to memory location [%i] for position [%i]",
                 c,chunk_pos->local_pos,p);
        DEBUG_INFO(message);
        chunk_pos->mem[chunk_pos->local_pos] = c;
        return;
    }

    /* The character wont fit in this chunk
     * Check the length of the next chunk, if there is one.
     * if the next chunk has space, shift the excess chars from the current 
     * chunk to the next chunk. If it doesn't have space, create a new chunk 
     */

    /* I dont think this code is reachable now */
    if(chunk_pos->next==NULL)
    {
        priv_insert_chunk_after(chunk_pos,buffer);
        chunk_pos->next->mem[0] = c;
        INCREMENT_LENGTH(chunk_pos->length,chunk_pos->length_sans_newline,c);
        return;
    }

    assert(chunk_pos->next != NULL);
    /* There is a next chunk and it has space */
    if(chunk_pos->next->length < CHUNK_SIZE-1)
    {
        priv_shift_chunk_right(chunk_pos->next);
        chunk_pos->next->mem[0] = chunk_pos->mem[CHUNK_SIZE-1];
        /* Make a space for our char */
        priv_shift_chunk_right_partial(chunk_pos,chunk_pos->local_pos);
        chunk_pos->mem[chunk_pos->local_pos] = c;

        INCREMENT_LENGTH(chunk_pos->length,chunk_pos->length_sans_newline,c);
        return;
    }
    
    /* Next buffer has no space!
     * lets insert a new buffer then */
    if(chunk_pos->next->length>=CHUNK_SIZE-1)
    {
        priv_insert_chunk_after(chunk_pos,buffer);

        /* Kick the last char over */
        chunk_pos->next->mem[0] = chunk_pos->mem[CHUNK_SIZE-1];

        if(chunk_pos->local_pos != chunk_pos->length)
            priv_shift_chunk_right_partial(chunk_pos,chunk_pos->local_pos);

        chunk_pos->mem[chunk_pos->local_pos] = c;
        return;
    }
#endif 
}

/*
 * Add a newline - split the chunk
 */
void mem_add_newline(buffer_t *buffer, CHARACTER c, POSITION p)
{}


/*
 * Remove a character 
 */
void delChar(buffer_t* buffer, POSITION p)
{}

/*
 * Add a string to the buffer, starting from POSITION
 */
void addBufferStr(buffer_t* buffer, CHARACTER* c , POSITION p)
{}

/*---------------------------------------------------------------------------*/

/*
 * Private Function Implementations
 */

chunk_t* priv_new_chunk()
{
    DEBUG_LOC();
    chunk_t* chunk_tmp = (chunk_t*)calloc(1,sizeof(chunk_t));

    chunk_tmp->type = STANDARD_LINE;
    chunk_tmp->length=0;
    chunk_tmp->length_sans_newline=0;
    chunk_tmp->local_pos=0;
    chunk_tmp->mem = (char *)calloc(CHUNK_SIZE,sizeof(char));

    chunk_tmp->next = NULL;
    chunk_tmp->prev = NULL;

    chunk_tmp->fold = NULL;
    chunk_tmp->folded_chunks=0;

    return chunk_tmp;
}

void priv_shift_chunk_right(chunk_t* chunk)
{
    DEBUG_LOC();
    /* Assertions */
    assert(chunk->length!=0);
    assert(chunk->length!=CHUNK_SIZE);
    assert(chunk!=NULL);

    priv_shift_chunk_right_partial(chunk,0);
}


void priv_shift_chunk_right_partial(chunk_t* chunk, int point)
{
    DEBUG_LOC();
    /* Assertions */
    assert(chunk!=NULL);
    assert(point<CHUNK_SIZE);
    assert(chunk->length > point);

    /* Variables */
    int i;

    for(i=chunk->length -1; i >= point; i--)
        chunk->mem[i+1] = chunk->mem[i];

    INCREMENT_LENGTH(chunk->length,chunk->length_sans_newline,'0');
}

void priv_free_chunk(chunk_t* chunk)
{
    DEBUG_LOC();
    /* Assertions */
    assert(chunk!=NULL);

    /* Variables */
    syntax_token_t* sx_ptr;
    syntax_token_t* sx_death;

    /*
     * Uh oh. 
     */
    if(chunk->prev!=NULL)
    {
        DEBUG_WARN("Chunk not isolated. munching anyway");
    }

    /* Remove syntax tokens from the chunk */
    sx_ptr = chunk->syn;
    sx_death = sx_ptr;
    while(sx_ptr!=NULL)
    {
        sx_death = sx_ptr;
        sx_ptr = sx_ptr->next;
        sx_ptr->prev=NULL;
        syn_delToken(sx_death);
    }

    if(chunk->next!=NULL)
        chunk->next->prev=NULL;

    free(chunk->mem);
    free(chunk);
}

void priv_chop_chunk(chunk_t* chunk, buffer_t* buffer)
{
    DEBUG_LOC();
    /* Assertions */
    assert(chunk!=NULL);
    assert(buffer!=NULL);
    assert(buffer->num_chunks>0);

    /* Do Stitching */
    chunk->prev->next = chunk->next;
    chunk->next->prev = chunk->prev;

    priv_free_chunk(chunk);

    buffer->num_chunks--;
}

/* TODO: Use the contextual pointers in buffer_t to optimise */
chunk_t* priv_get_chunk_by_position(buffer_t* buffer, POSITION p)
{
    /* We are using POSITION. This means that it is a location supplied
     * from the front end, which by definition means that we do _not_ 
     * count folded lines we also set a "cursor" variable within the chunk 
     * which points to the exact position the user required.
     */
    DEBUG_LOC();

    /* Assertions */
    assert(buffer!=NULL);
    assert(buffer->head!=NULL);

    /* Variables */
    POSITION display_count = 0;
    POSITION prev_display_count = 0; /* lags one behind */
    int chunk_counter = 0;
    chunk_t* chunk_ptr;

    /* Debugging Variables */
    chunk_ptr=buffer->head;
    char message[80];

    assert(chunk_ptr->length<=CHUNK_SIZE);
    /* Debugging */
#if 0

    sprintf(message,"Position [%i], chunk->length [%i]",
            p,chunk_ptr->length);
    DEBUG_INFO(message);
#endif

    /* Work on the first chunk */
    if( p<CHUNK_SIZE
        && chunk_ptr->next==NULL)
    {
        chunk_ptr->local_pos=p;
        sprintf(message," [1] Returning Chunk Number [%i], chunk->local_pos [%i]",
                chunk_counter,chunk_ptr->local_pos);
        DEBUG_INFO(message);
        return chunk_ptr;
    }

    while(chunk_ptr->next!=NULL)
    {
        if(display_count >= p)
        {
            chunk_ptr->local_pos = p - prev_display_count;
            sprintf(message," [2] Returning Chunk Number [%i], chunk->local_pos [%i]",
                    chunk_counter,chunk_ptr->local_pos);
            DEBUG_INFO(message);

            return chunk_ptr;
        }

        display_count += chunk_ptr->length_sans_newline;

        if(chunk_ptr->prev!=NULL)
            prev_display_count += chunk_ptr->prev->length_sans_newline;
#if 0

        if(chunk_ptr->next==NULL)
        {
            /* Ok, to reach this point the requested character
             * is the first of a yet to be created chunk */
            DEBUG_INFO("Creating New Chunk");
            priv_insert_chunk_after(chunk_ptr,buffer);
            chunk_ptr->next->local_pos=0;
            return chunk_ptr->next;
        }
#endif
        chunk_ptr=chunk_ptr->next;
        chunk_counter++;
    }

    /* If the loop exits here then the character
     * is the first of a new chunk, which we will need to create
     */
    DEBUG_INFO("LOOP FALL THROUGH");
    priv_insert_chunk_after(chunk_ptr,buffer);
    chunk_ptr = chunk_ptr->next;
    
    chunk_ptr->local_pos=0;

    return chunk_ptr;
}
void priv_insert_chunk_after(chunk_t* chunk, buffer_t* buffer)
{
    DEBUG_LOC();
    chunk_t* chunk_tmp = priv_new_chunk();
    int type;

    assert(chunk!=NULL);

    type = chunk->type;

    /* Inserting at end */
    if(chunk->next==NULL)
    {
        chunk->next = chunk_tmp;
        chunk_tmp->next = NULL;
        chunk_tmp->prev = chunk;
    }
    else
    {
        chunk_tmp->next = chunk->next;
        chunk_tmp->prev = chunk;
        chunk->next->prev = chunk_tmp;
        chunk->next = chunk_tmp;
    }

    if(type==FOLD_START||type==FOLDED)
    {
        chunk_tmp->type = FOLDED;
    }

    buffer->num_chunks++;
}
